package com.LensBlaster;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.ViewSwitcher;
import android.widget.Gallery.LayoutParams;

import com.LensBlaster.upload.Flickr.Upload;

/**
 * Displays a single photo within an album. Also displays an
 * image switcher at the bottom of the frame with thumbnails
 * of other pictures within the album. This class is tied to 
 * LensBlasterDB class for information retrieval. To display
 * an photo, the receiving bundle must contain the following:
 * an album ID, photo ID, image URI, and the position in the
 * album itself. 
 * 
 * This class duplicates a significant portion of code in the 
 * ImageAdapter member class with PhotoView, to be refactored
 * for 2.0.
 * 
 * @author Jake
 */
public class PhotoView  extends Activity implements
		AdapterView.OnItemSelectedListener, ViewSwitcher.ViewFactory {
	private static final int ACTIVITY_UPLOAD = 1;
	private static final int PHOTO_UPLOAD_ID = Menu.FIRST;
	private static final int ADD_TO_ALBUM_ID = Menu.FIRST + 1;
	private static final int PHOTO_DELETE_ID = Menu.FIRST + 2;
	
	private ImageSwitcher mSwitcher;
	private LensBlasterDB mAlbumDB = null;
	private Cursor mAlbumsCursor;
	private Uri mImageURI;
	private long mPhotoID;
	private long mAlbumID;

	/**
	 * Called when the class is first created.
	 * 
	 * The bundle passed must contain the following:
	 * <ul>
	 * <li> Album ID (String) registered in the Albums table of
	 * of LensBlasterDB, and passed in as an extra, retrieved
	 * with key LensBlasterDB.ALBUM_KEY_ROWID.</li>
	 * 
	 * <li> Photo ID (long) referring to a photo registered in
	 * the PhotoAlbum table of LensBlasterDB, also passed in
	 * as and extra, retrieved with key 
	 * LensBlasterDB.PA_KEY_PHOTOID. </li>
	 * 
	 * <li> URI of the photo, passed on the data field of the 
	 * Intent that called this class. </li>
	 * 
	 * <li> Position within the calling view, passed on the
	 * bundle of the intent, retrieved with the "position"
	 * String key.</li>
	 * </ul>
	 * @param savedInstanceState see above
	 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		setContentView(R.layout.photo_view);
		
		mAlbumDB = new LensBlasterDB(this);
		mAlbumDB.open();

		mSwitcher = (ImageSwitcher) findViewById(R.id.switcher);
		mSwitcher.setFactory(this);
		//TODO: formatting stuff, need shove to XML
		mSwitcher.setInAnimation(AnimationUtils.loadAnimation(this,
				android.R.anim.fade_in));
		mSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this,
				android.R.anim.fade_out));
		
		Intent incoming = this.getIntent();
		mImageURI = incoming.getData();
		mSwitcher.setImageURI(mImageURI);
		
		Bundle b = incoming.getExtras();

		mPhotoID = Long.parseLong(b.getString(LensBlasterDB.PA_KEY_PHOTOID));
		mAlbumID = b.getLong(LensBlasterDB.ALBUM_KEY_ROWID);
		int position = b.getInt("position");
		
		Gallery g = (Gallery) findViewById(R.id.gallery);
		g.setAdapter(new ImageAdapter(this));
		g.setOnItemSelectedListener(this);
		g.setSelection(position);
	}

	/**
	 * Handler to display new full-screen image, called by the Gallery object every
	 * time we wish to replace the image displayed.
	 * @param parent The ImageView where the selection happened.
	 * @param v The view within the ImageView that was clicked.
	 * @param position The position of the view in the adapter.
	 * @param id The row id of the item that is selected.
	 */
	public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
		Cursor thumbnail = managedQuery(
				MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, null,
				MediaStore.Images.Thumbnails.IMAGE_ID + "='" + id + "'", null, null);
		thumbnail.moveToFirst();
		
		int dataCol;
		String thumbData;
		
		try {
			dataCol = thumbnail.getColumnIndex(MediaStore.Images.Thumbnails.DATA);
			thumbData = thumbnail.getString(dataCol);
		} catch (CursorIndexOutOfBoundsException e) {
			thumbnail.close();

			thumbnail = managedQuery(
					MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null,
					MediaStore.Images.ImageColumns._ID + "='" + id + "'", null, null);
			thumbnail.moveToFirst();
			
			dataCol = thumbnail.getColumnIndex(android.provider.MediaStore.Images.Media.DATA);
			thumbData = thumbnail.getString(dataCol);
		}
		thumbnail.close();

		mImageURI = Uri.parse(thumbData);
		mPhotoID = id;
		mSwitcher.setImageURI(mImageURI);
	}

	/**
	 * Required, but unused.
	 */
	public void onNothingSelected(AdapterView<?> parent) { } //required
	
	/**
	 * We construct the menu options here.
	 * @param menu The menu to which we add menu items.
	 * @return True to display the menu; false to keep it hidden.
	 */
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		menu.add(0, PHOTO_UPLOAD_ID, 0, R.string.menu_photo_upload);
		menu.add(0, ADD_TO_ALBUM_ID, 0, R.string.menu_add_to_album);
		if (mAlbumID != LensBlasterDB.ALL_PHOTOS_ALBUM_ID)
			menu.add(0, PHOTO_DELETE_ID, Menu.NONE, R.string.menu_photo_delete);
		return true;
	}
	
	/**
	 * Handles menu selection event
	 * @param featureId ignore this
	 * @param item position of menu item selected
	 * @return boolean Return true to finish processing of selection, or false to perform the normal menu handling (calling its Runnable or sending a Message to its target Handler).
	 */
	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		switch (item.getItemId()) {
		case PHOTO_UPLOAD_ID:
			uploadPhoto(mImageURI);
			return true;
		case ADD_TO_ALBUM_ID:
			addToAlbum();
			return true;
		case PHOTO_DELETE_ID:
			deletePhoto();
			return true;
		}
		return super.onMenuItemSelected(featureId, item);
	}
	
	/**
	 * Utility class to upload a file
	 * @param uri Uri of the image we wish to upload.
	 */
	private void uploadPhoto(Uri uri) {
		Intent i = new Intent(this, Upload.class);
		i.setData(uri);		
		startActivityForResult(i, ACTIVITY_UPLOAD);
	}

	/**
	 * Handles add to album action. 
	 * Precondition:
	 * <ul>
	 * <li>mAlbumDB is open</li>
	 * <li>mAlbumsCursor points to the photo we're currently using</li>
	 * </ul>
	 * Postcondition:
	 * <ul>
	 * <li>The chosen album now has the selected picture, but not a duplicate entry, <em>OR</em></li>
	 * <li>nothing has happened, because the user cancelled.</li>
	 * </ul>
	 */
	private void addToAlbum() {
		mAlbumsCursor = mAlbumDB.fetchAllAlbums();
		startManagingCursor(mAlbumsCursor);
		Cursor c = mAlbumsCursor;
		
		final int idIndex = c.getColumnIndex(LensBlasterDB.ALBUM_KEY_ROWID);
		final int titleIndex = c.getColumnIndex(LensBlasterDB.ALBUM_KEY_TITLE);

		final List<CharSequence> albumTitles = new ArrayList<CharSequence>();
		final List<Integer> albumIDs = new ArrayList<Integer>();
		final List<Integer> addPhotoToAlbums = new ArrayList<Integer>();
		
		//build a list of albums titles and id's, excluding this one.
		while(c.moveToNext()) {
			int albumID = (int) c.getLong(idIndex);
			if ( mAlbumID != albumID ) {
				albumTitles.add(c.getString(titleIndex));
				albumIDs.add(albumID);
			}
		}

		if (albumTitles.size() > 0) { //do we have any albums to add this photo to, excluding this one?
			new AlertDialog.Builder(this)
			.setTitle("Choose albums:").setMultiChoiceItems(albumTitles.toArray(new CharSequence[albumTitles.size()]), null,
				new DialogInterface.OnMultiChoiceClickListener() {
					public void onClick(DialogInterface dialog, int which, boolean checked) { 
						if (checked)
							addPhotoToAlbums.add(albumIDs.get(which));
						else
							addPhotoToAlbums.remove(albumIDs.get(which));
					}
				})
			.setCancelable(true)
			.setPositiveButton("OK", 
				new DialogInterface.OnClickListener() { 
					public void onClick(DialogInterface dialog, int which) { 
						dialog.dismiss();
						for(Integer albumID : addPhotoToAlbums) {
							mAlbumDB.addPhotoToAlbum(mPhotoID, albumID);
						}
					}
				})
				.setNegativeButton("Cancel", 
					new DialogInterface.OnClickListener() { 
						public void onClick(DialogInterface dialog, int which) { 
							dialog.dismiss(); 
						}
				})
			.create()
			.show();
		}
	}
	
	/**
	 * Private helper function to delete a photo. <br>
	 * Preconditions:
	 * <ul>
	 * <li>mAlbumDB is open</li>
	 * <li>mAlbumID is a valid ID on the Albums table, and not equal to LensBlasterDB.ALL_PHOTOS_ALBUM_ID</li>
	 * <li>mPhotoID is a valid ID on the photoalbum table.</li>
	 * </ul>
	 * 
	 * Postconditions:
	 * <ul>
	 * <li>mPhotoID no longer exists on the photoalbum table.</li>
	 * </ul>
	 */
	private void deletePhoto() {
		new AlertDialog.Builder(this)
		.setMessage("Delete this photo?")
		.setPositiveButton("Delete",
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					dialog.dismiss();
					
					mAlbumDB.deletePhotoFromAlbum(mPhotoID, mAlbumID);
					
					Intent mIntent = new Intent();
			        setResult(RESULT_OK, mIntent);
					finish(); 	// return to album view
				}
			})
		.setCancelable(true)
		.setNegativeButton("Cancel",
						new DialogInterface.OnClickListener() {
							public void onClick(DialogInterface dialog,
									int which) {
								dialog.dismiss();
							}
						})
		.create()
		.show();
	}
	
	/**
	 * Called by the image switcher when needed (not sure when).
	 * @return a new ImageView object to our formatting that the calling class inflates.
	 */
	public View makeView() {
		ImageView i = new ImageView(this);
		i.setBackgroundColor(0xFF000000);
		i.setScaleType(ImageView.ScaleType.FIT_CENTER);
		i.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT,
				LayoutParams.FILL_PARENT));
		return i;
	}
	
	/**
	 * Called when destroying this activity.
	 * Postcondition:
	 * <ul>
	 * <li>mDbHelper is closed</li>
	 * <li>mSwitcher == null</li>
	 * <ul>
	 */
	@Override
	public void onDestroy(){
		super.onDestroy();
		android.util.Log.d("lens","Destroying PhotoView");
		mSwitcher = null;
		mAlbumDB.close();
	}
	
	/**
	 * Handles a return to this activity from the activity we called.
	 * @param requestCode The code of the request we packaged into the Intent that opened the Activity we called.
	 * @param resultCode The code of the result (i.e., OK).
	 * @param intent The initial intent we passed into the activity we called. Ignored.
	 * @author Michael
	 */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
		if (intent!=null) {
			super.onActivityResult(requestCode, resultCode, intent);

			switch(requestCode) {
			case ACTIVITY_UPLOAD:
				if (resultCode == Activity.RESULT_OK){
					Toast t = Toast.makeText(this, "Photo uploaded.", 3);
					t.show();
				} else {
					new AlertDialog.Builder(this)
					.setMessage("Error authorizing LensBlaster.")
					.setPositiveButton("Try Again", 
						new DialogInterface.OnClickListener() {
							public void onClick(DialogInterface dialog, int whichButton) {
								dialog.dismiss();			
								uploadPhoto(mImageURI);
							}
						})
					.setCancelable(true)
					.setNegativeButton("Cancel",
									new DialogInterface.OnClickListener() {
										public void onClick(DialogInterface dialog,
												int which) {
											dialog.dismiss();
										}
									})
					.create()
					.show();
				}
				break;
			}
		}
	}
	
	/**
	 * Inner class that prepares photo thumbnails for the GridView.
	 */
	public class ImageAdapter extends BaseAdapter {
		private final Context mContext;
		
		public ImageAdapter(Context c) {
			mContext = c;
			
		}

		/**
		 * How many items are in the data set represented by this ImageAdapter.
		 * @return Count of items.
		 */
		public int getCount() {
			Cursor c = mAlbumDB.fetchPhotosByAlbum(mAlbumID);

			if (c == null) {
				android.util.Log.d("lens", "No photos found for album");
				return 0;
			} else {
				int count = c.getCount();
				c.close();
				return count;
			}
		}

		/**
		 * Get the position associated with the specified position in the album.
		 * Never used.
		 * @param The position associated with the specified position in the album.
		 * @return The position passed in.
		 */
		public Object getItem(int position) {
			return position;
		}

		/**
		 * Retrieves the ID of the photo of the thumbnail we wish to display.
		 * @param position the position of the thumbnail we wish to display.
		 * @return the ID of the photo of the thumbnail we wish to display.
		 */
		public long getItemId(int position) {
			Cursor c = mAlbumDB.fetchPhotosByAlbum(mAlbumID);
			c.moveToPosition(position);
			String photoID = c.getString(c.getColumnIndex(LensBlasterDB.PA_KEY_PHOTOID));
			c.close();

			return Long.valueOf(photoID);
		}

		/**
		 * Get a View that displays the photo thumbnail at the specified position in the data set. 
		 * @param position The position of the photo within the adapter's data set whose thumbnail 
		 * we want.
		 * @param convertView The old view to reuse, if possible. Note: You should check that this 
		 * view is non-null and of an appropriate type before using. If it is not possible to 
		 * convert this view to display the correct data, this method can create a new view.
		 * @return view A View with the thumbnail we requested.
		 */
		public View getView(int position, View convertView, ViewGroup parent) {
			if (convertView!=null)
				return (ImageView) convertView;
			
			final String photoID = Long.toString(getItemId(position));
			
			Cursor photo = managedQuery(
					MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null,
					MediaStore.Images.ImageColumns._ID + "=" + "'" + photoID + "'", null, null);
			startManagingCursor(photo);
			photo.moveToFirst();
			
			Cursor thumbnail = managedQuery(
					MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, null,
					MediaStore.Images.Thumbnails.IMAGE_ID + "=" + "'" + photoID + "'", null, null);
			startManagingCursor(thumbnail);
			thumbnail.moveToFirst();

			int dataColIdx;			
			String imageData;
			
			try { 
				//trying to fetch the photo's associated thumbnail Uri
				dataColIdx = thumbnail.getColumnIndex(android.provider.MediaStore.Images.Thumbnails.DATA);
				imageData = thumbnail.getString(dataColIdx); //Throws CursorIndexOutOfBoundsExceptions
			} catch (CursorIndexOutOfBoundsException e) {
				try { 
					//when that fails, manually generate the thumbnail, and try again
					MediaStore.Images.Media.insertImage(
							mContext.getContentResolver(), 
							photo.getColumnName(photo.getColumnIndex(MediaStore.Images.Media.DATA)), 
							"", "");
					thumbnail.requery();
					thumbnail.moveToFirst();
					
					imageData = thumbnail.getString(thumbnail.getColumnIndex(MediaStore.Images.Media.DATA));
				} catch (FileNotFoundException f) { 
					//at the very least, just fetch the actual photo
					//this is bad because it's processor- and memory-intensive, but
					//chances of getting this far are rare
					//TODO: turn this into a proxy process, and generate the images in the background
					dataColIdx = photo.getColumnIndex(android.provider.MediaStore.Images.Media.DATA);
					imageData = photo.getString(dataColIdx);
				}
			}

			ImageView i = new ImageView(mContext);

			//TODO: formatting stuff, need to shove this into an XML file
			Bitmap bm = BitmapFactory.decodeFile(Uri.parse(imageData).getPath());
			i.setImageBitmap(Bitmap.createScaledBitmap(bm, bm.getWidth()/4, bm.getHeight()/4, false));
			i.setAdjustViewBounds(true);
			i.setScaleType(ImageView.ScaleType.CENTER_CROP);
			i.setLayoutParams(new Gallery.LayoutParams(
					LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
			i.setBackgroundResource(R.drawable.picture_frame);
			
			return i;
		}  
	}
}